package org.lvxiao.example.redis.lock;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;

public class DistributionSynchronizedAdvice implements MethodInterceptor, InitializingBean, BeanFactoryAware {

	private DistributionLock lock;
	private ExpressionParser parser = new SpelExpressionParser();
	private BeanFactory beanFactory;

	public void setLock(DistributionLock lock) {
		this.lock = lock;
	}

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// TODO cache
		DistributionSynchronized annotation = resolveAnnotation(invocation);
		if (annotation == null) {
			invocation.proceed();
		}

		String prefix = annotation.prefix();
		String key = resolveKey(annotation.key(), invocation);
		long blockTime = annotation.blockTime();
		long expire = annotation.expire();
		String lockKey = prefix + "::" + key;
		Object result = null;
		String signature = null;
		try {
			signature = lock.lock(lockKey, expire, blockTime);
			result = invocation.proceed();
		} catch (Exception e) {
			throw e;
		} finally {
			if (signature != null) {
				lock.unlock(signature);
			}
		}
		return result;
	}

	public String resolveKey(String key, MethodInvocation invocation) {
		if (key == null || key.isEmpty()) {
			return "";
		}
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		Object[] arguments = invocation.getArguments();
		String[] parameters = u.getParameterNames(invocation.getMethod());
		
		Map<String, Object> contextMap = new HashMap<>();

		for (int i = 0; i < parameters.length; i++) {
			contextMap.put("p" + i, arguments[i]);
			contextMap.put(parameters[i], arguments[i]);
		}
		StandardEvaluationContext context = new StandardEvaluationContext();
		context.setVariables(contextMap);
		Expression exp = parser.parseExpression(key);
		String value = exp.getValue(context, String.class);
		return value == null ? "" : value;
	}

	public DistributionSynchronized resolveAnnotation(MethodInvocation invocation)
			throws NoSuchMethodException, SecurityException {
		Method method = invocation.getMethod();
		Class<?> targetClass = AopUtils.getTargetClass(invocation.getThis());
		Method targetMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());

		return targetMethod.getAnnotation(DistributionSynchronized.class);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		lock = beanFactory.getBean(DistributionLock.class);
		Assert.notNull(lock, "lock is null");
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

}
